net.quic/net.http: HTTP/3 server support, Phase 13 (13a-13d-1) - #28164
Draft
quaesitor-scientiam wants to merge 10 commits into
Draft
net.quic/net.http: HTTP/3 server support, Phase 13 (13a-13d-1)#28164quaesitor-scientiam wants to merge 10 commits into
quaesitor-scientiam wants to merge 10 commits into
Conversation
Scoping pass mapping the completed client against what server support needs is posted on the tracking issue (vlang#27675). Records the suggested 13a-13e sub-phase breakdown in PROGRESS.md, mirroring Phase 12's own 12a-12d convention; 13a (TLS 1.3 server handshake) starts now.
First slice of the TLS 1.3 server handshake (Phase 13a): build_server_hello and build_encrypted_extensions, the server-role mirror of the existing client-role build_client_hello. Both round-trip through this file's own parse_server_hello/parse_encrypted_extensions, which is a real cross-check since build and parse were each derived independently from the RFC 8446 text rather than from each other. Caught one real bug via the round-trip test before committing: a server's key_share extension is a bare KeyShareEntry (RFC 8446 SS4.2.8), not the list-wrapped KeyShareClientHello shape build_client_hello's own encode_key_share_extension produces -- needed a dedicated encode_key_share_extension_server. Same asymmetry already existed and was handled correctly for supported_versions (bare 2 bytes server-side vs. a length-prefixed list client-side); this is the identical class for key_share, not a new discovery about the protocol. build_encrypted_extensions enforces the two mandatory transport parameters RFC 9000 SS7.3 requires an endpoint (initial_source_connection_id) and a server specifically (original_destination_connection_id) to always include, mirroring build_client_hello's identical enforcement of its own mandatory field. Still to come within 13a: Certificate presentation, CertificateVerify signing, server Finished, HelloRetryRequest generation.
Completes the message-construction half of 13a's TLS 1.3 server handshake:
- encode_certificate (tls13_certificate.v): server Certificate message,
reusing the existing CertificateEntry/ParsedCertificate types. certificate_
request_context is always empty per RFC 8446 SS4.4.2 ("in the case of
server authentication, this field SHALL be zero length").
- encode_certificate_verify (tls13_certificate.v): signs
certificate_verify_signed_content(.server, ...) via crypto.ecdsa.PrivateKey.
sign() -- a pre-existing V primitive, untouched by net.quic work so far.
Only sig_scheme_ecdsa_secp256r1_sha256 is wired up; RSA-PSS signing is
explicitly rejected with a clear error rather than silently mis-signing --
it needs a mbedtls_pk_sign_ext V wrapper that doesn't exist yet (only the
verify side, verify_rsa_pss_signature, does).
- build_finished (tls13_messages.v): thin wrapper around the already
side-agnostic compute_finished_verify_data. Verified against the real RFC
8448 SS3 vector, not just round-tripped against this module's own parser.
- build_hello_retry_request (tls13_server_hello.v): shares ServerHello's wire
type, distinguished by the fixed magic random. key_share carries a bare
NamedGroup (RFC 8446 SS4.2.8's KeyShareHelloRetryRequest) -- a third,
distinct wire shape from both the client's and the real-ServerHello
key_share encodings.
Every function round-trips through its already-existing, independently-
written parse counterpart -- a real cross-check, not tautological, since
build and parse were each derived from the RFC text separately. Full
net.quic suite 54/54, ./vnew missdoc clean, ./vnew fmt -w applied.
Certificate/CertificateVerify signature framing is verified; the produced
ECDSA signature's cryptographic validity is not cross-verified against an
independent verifier in this repo (no PublicKey.verify() in crypto.ecdsa, no
EC certificate fixture) -- the same documented gap Phase 2c's own
x509_standalone_signature_test.v already states for the identical reason.
Still to come within 13a: the server-side state machine wiring these five
functions into an actual handshake driver (see PROGRESS.md).
Tls13ServerHandshake (tls13_server_handshake.v), the server-role mirror of the existing Tls13ClientHandshake. respond_to_client_hello parses and fully validates an incoming ClientHello (cipher suite, TLS 1.3 offered, secp256r1 key_share, ecdsa_secp256r1_sha256 in signature_algorithms, ALPN common protocol, and RFC 9000 SS7.3's transport-parameter role restrictions) BEFORE allocating anything, does real ECDH against the client's offered key_share, then builds the entire response flight -- ServerHello through this server's own Finished -- in one call, since nothing arrives from the peer in between. process_finished verifies the client's Finished and confirms the handshake. Needed ClientHello parsing, which didn't exist at all before this commit: added parse_client_hello, decode_alpn_offer, parse_key_share_extension_client, parse_signature_algorithms_extension_client, and parse_supported_versions_from_client to tls13_client_hello.v. Each client-vs- server wire-shape asymmetry already established for ServerHello (list-wrapped vs. bare key_share, versioned list vs. bare selected-version) recurs here in reverse, documented the same way. Verified with a real integration test, not just round-tripped against this module's own code: a genuine Tls13ClientHandshake and Tls13ServerHandshake, each independently written against the RFC text, run against each other with fresh ECDHE keys on both sides (not fixed RFC 8448 vectors). The client's own real process_server_hello/process_encrypted_extensions/verify_finished all independently accept this server's real output, and this server's process_finished accepts a real client Finished built the same way -- confirming the ECDH, key schedule, and both Finished computations genuinely agree between two separately-implemented roles. Full net.quic suite 55/55, ./vnew missdoc clean, ./vnew fmt -w applied. Not exercised: Certificate/CertificateVerify chain verification end-to-end (no EC certificate fixture in this repo -- same documented gap as encode_certificate_verify's own tests); HelloRetryRequest generation is not wired into this state machine (a key_share group mismatch is a hard failure) -- the same deliberate-defer scope choice Tls13ClientHandshake. process_server_hello already made for its own first-HRR gap. This completes 13a's stated scope in PROGRESS.md. Next: 13b (Retry + address validation).
Adds real cryptographic verification of encode_certificate_verify's output via crypto.ecdsa's PublicKey.verify() -- which already exists (vlib/crypto/ ecdsa/ecdsa.v:340) and was previously missed by an incomplete grep for the wrong receiver variable name, not actually absent. The prior round-trip test only confirmed wire framing and non-constancy; this closes the real gap: proving the signed content, key, and DER encoding all genuinely agree, not just that the bytes look plausible. Two new assertions: a wrong-transcript-hash/signature pairing must NOT verify (rules out a check that ignores the content), and a signature must be rejected by a DIFFERENT key's public half (rules out a verify() that accepts anything). This is a same-library round trip (OpenSSL signs, OpenSSL verifies) -- independent-library cross-verification via a real peer's mbedTLS still isn't exercised, since this repo has no EC certificate fixture to build an mbedtls_pk_context from. That remaining gap is unchanged and still documented; only the previously-incorrect "no PublicKey.verify() exists at all" claim is fixed. Full net.quic suite 55/55.
encode_certificate_verify's doc comment stated as settled fact that its
OpenSSL-produced DER signature is directly compatible with net.mbedtls's
verify_ecdsa_signature ("no reformatting needed between the two
libraries"). That's the expected, standard behavior (OpenSSL's default EC
signing format and mbedTLS's ECDSA verification both use ASN.1 DER
ECDSA-Sig-Value, the TLS/X.509 convention), but this repo has never
actually tested it: there's no EC certificate fixture to build an
mbedtls_pk_context from for a real cross-library check, only a same-library
(OpenSSL signs, OpenSSL verifies) round trip. Reworded to state what's
actually verified (source inspection + the same-library test) versus what's
expected-but-untested, rather than asserting settled fact.
Caught during a requested pass verifying claims made across this PR's
commits/comments. Everything else checked (RSA-PSS signing wrapper
non-existence, EC certificate fixture non-existence, the three distinct
key_share wire shapes, the supported_versions client/server asymmetry, and
the application-secrets-derive-after-server-Finished timing) was confirmed
accurate against the cached primary RFC text directly.
Completes Phase 13b (Retry + address validation, RFC 9000 SS8/SS8.1/SS17.2.5): - encode_retry_packet (retry.v): builds a complete Retry packet, reusing compute_retry_integrity_tag directly (already side-agnostic -- no new crypto needed for the tag itself). Round-trips through the already- existing, independently-written client-role verify_retry_integrity_tag/ parse_retry_packet -- proving the exact code that will receive this in production actually accepts it, not just that it looks well-formed. - generate_retry_token/validate_retry_token/validate_retry_token_for_attempt (retry_token.v, new): AEAD-sealed (AES-128-GCM) address-validation tokens. AEAD authentication alone satisfies RFC 9000 SS8.1.4's "difficult to guess" and integrity requirements -- no separate random component needed beyond the nonce GCM itself requires. validate_retry_token_for_attempt adds the two context-dependent checks SS8.1.4 calls for: bound client address must match, and a short expiry window. NEW_TOKEN-frame issuance (SS8.1.3, tokens reusable across future connections) is out of scope -- v1 only issues tokens via Retry. Full single-use replay tracking beyond the expiry window is deferred to 13d (needs a real listening socket to own a consumed-token cache's lifetime); the short window satisfies SS8.1.4's "prevented OR limited" replay requirement in the interim. - AntiAmplificationLimiter (anti_amplification.v, new): RFC 9000 SS8.1's 3x pre-validation send limit, deliberately mirroring flow_control.v's FlowControlWindow shape. Standalone and tested; not yet wired into any datagram-processing loop, since that loop doesn't exist until 13d. Found and flagged (not fixed here, out of scope): while choosing a CSPRNG for the token nonce, discovered conn.v's dial() uses V's general-purpose `rand` module (wyrand-backed, not cryptographically secure) for original_dcid/scid/client_random -- all security-relevant values that should use crypto.rand instead (identical API, OS-backed, already used elsewhere in this codebase). Real gap in already-merged Phase 9 code (PR vlang#28129), flagged as a separate follow-up task. Full net.quic suite 57/57, ./vnew missdoc clean, ./vnew fmt -w applied.
…NECTION_ID) Adds the RFC 9000 §19.15/§19.16 wire codec for NEW_CONNECTION_ID/ RETIRE_CONNECTION_ID frames (frame.v), previously falling through parse_frame's generic "not yet implemented" branch, plus generate_stateless_reset_token (stateless_reset.v) implementing RFC 9000 §10.3.2's recommended HMAC-SHA-256(static_key, connection_id) derivation, cross-checked against the existing StatelessResetTracker.is_stateless_reset. Also updates QuicConn.dispatch_one_rtt_frame (conn.v) to explicitly acknowledge the two new frame types now flow through it instead of silently landing in the generic informational-hint else-arm, documenting why they're accepted-but-not-yet-acted-upon pending the (deliberately deferred) active-connection-ID-set state machine. Driving that active set (issuing more CIDs, active_connection_id_limit accounting) remains out of scope, same as before this commit.
…o QuicConn Adds server-role support to QuicConn (previously client-only) plus a new accept() constructor mirroring dial(): role-aware directional key selection, role-branched handshake dispatch (dispatch_server_handshake_message), role-asymmetric handshake-confirmation semantics (RFC 9001 §4.1.2), and server-side HANDSHAKE_DONE sending -- all reusing the existing client-role packet-building/CRYPTO-reassembly/drain machinery. Two RFC-conformance bugs found and fixed via an adversarial multi-agent review before commit, both independently re-verified: - RFC 9000 §7.2: a client's Initial-space packets address their DCID field to its own original_dcid until it has processed a reply, so the DCID match in process_initial_or_handshake now accepts a server-role connection's bootstrap ClientHello even though the server's real scid didn't exist yet when the client sent it. - RFC 9001 §4.9.1: Initial-key discard is send-triggered for a client but receive-triggered for a server -- applying the client's trigger to both roles made accept()'s single poll() call discard the server's Initial keys before the client had sent anything back, silently dropping any ClientHello retransmission and blocking the server's own PTO retransmission of its first flight. Split into a client-only send trigger (build_handshake_packet) and a new server receive trigger (process_initial_or_handshake, right after a Handshake-space packet from the client decrypts). Also fixes a missing RFC 9000 §14.1 anti-amplification check: accept() now rejects any datagram under the 1200-byte floor before doing any work, closing a reflection-amplification gap. accept() deliberately does not decide Retry-vs-direct-accept policy (deferred to 13d-2's UDP listener, which has the cross-connection-attempt state that decision needs) and does not fragment a large certificate chain's Handshake-space CRYPTO flight across multiple packets (documented scope limits, not blockers for this repo's own small test certificate). Tests: new accept_test.v drives a real dial()/accept() pair through a full handshake (including real Certificate/CertificateVerify chain verification against a freshly generated EC cert -- 13a's own PROGRESS.md noted gap) and bidirectional stream exchange, plus regression assertions for both RFC-conformance fixes above and a rejection test for the anti-amplification floor. Full suite: 58/58 passing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Documentation-only update following 3a97c59. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase 13 (server support) for HTTP/3, tracked at #27675. Sub-phases 13a, 13b,
13c, and 13d-1 are done so far, following the suggested 13a-13e sequence from
the scoping comment on #27675 — same one-sub-phase-per-stacked-PR convention
Phase 12 used for 12a-12d. 13d was split into 13d-1 (this section) and 13d-2
once 13d-1 turned out to be substantially larger than originally scoped:
QuicConnhad zero server-role support going in (dial()hardcoded.client, the handshake dispatch only knew the client's flow), so wiring areal server-role handshake path in was its own sub-phase before the UDP
listener/demux (13d-2) has anything to call.
The client (Phases 0-12) merged in #28129. Its core QUIC layer is already
role-parameterized (
role QuicRoleonQuicConn;is_locally_initiated/initial_*_limit_for_streamalready take a role explicitly), so this phaseis additive on top of it, not a rework.
13a: TLS 1.3 server handshake — DONE
Message construction (
7b45b39bc7,47254d7dea):build_server_hello+build_encrypted_extensionsencode_certificate(Certificate presentation)encode_certificate_verify— signing (ECDSA P-256 only for now;RSA-PSS needs a
mbedtls_pk_sign_extV wrapper that doesn't exist yet)build_finished(verified against the real RFC 8448 §3 vector)build_hello_retry_requestServer-side state machine (
9ecb42eca7):Tls13ServerHandshake(tls13_server_handshake.v) —respond_to_client_helloparses+fully validates an incomingClientHello, does real ECDH, builds the entire response flight
(ServerHello through this server's own Finished) in one call.
process_finishedverifies the client's Finished and confirms thehandshake.
parse_client_hello+ 4 supporting decoders added totls13_client_hello.v(didn't exist before — only the client-sidebuilder existed).
Every message-construction function round-trips through its
already-existing, independently-written parse counterpart (caught one real
bug this way — server
key_shareis a bareKeyShareEntry, not theclient's list-wrapped shape). The state machine is additionally verified by
a real client-vs-server integration test: a genuine
Tls13ClientHandshakeandTls13ServerHandshake, each independentlywritten against the RFC text, run against each other with fresh ECDHE keys
on both sides (not fixed vectors) — the client's own real
process_server_hello/process_encrypted_extensions/verify_finishedallindependently accept this server's real output, and vice versa for the
client's Finished.
CertificateVerify's ECDSA signature is independently, cryptographically
verified (
01f754809d) viacrypto.ecdsa.PublicKey.verify()— proving thesigned content, key, and DER encoding genuinely agree, plus that a
wrong-content/signature pairing and a wrong-key verification both correctly
fail. This is a same-library (OpenSSL signs, OpenSSL verifies) check, not
independent-library cross-verification the way a real peer's mbedTLS
eventually gets — this repo still has no EC certificate fixture to build an
mbedtls_pk_contextfrom for that.Known, deliberate scope limits (documented in code, not hidden):
rejected with a clear error, not silently mis-signed. No caller in this
codebase uses RSA keys anywhere, so this is deliberately deferred rather
than built speculatively.
end-to-end (no EC certificate fixture in this repo) — the same gap Phase
2c's own
x509_standalone_signature_test.vdocuments for the identicalreason.
mismatch is a hard failure.
build_hello_retry_requestexists and isunit-tested at the message layer. This is the SAME deliberate-defer scope
choice
Tls13ClientHandshake.process_server_helloalready made for itsown first-HRR gap (client side never generates a ClientHello2 either).
13b: Retry + address validation — DONE (
d29851d6bc)encode_retry_packet(retry.v) — builds a complete Retry packet,reusing
compute_retry_integrity_tagdirectly (already side-agnostic— no new crypto needed for the tag). Round-trips through the
already-existing, independently-written client-role
verify_retry_integrity_tag/parse_retry_packet— proving the exactcode that will receive this in production accepts it.
generate_retry_token/validate_retry_token/validate_retry_token_for_attempt(retry_token.v, new) —AEAD-sealed (AES-128-GCM) address-validation tokens. AEAD
authentication alone satisfies RFC 9000 §8.1.4's "difficult to guess"
and integrity requirements.
validate_retry_token_for_attemptaddsthe two context-dependent checks §8.1.4 calls for: bound client
address must match, and a short expiry window.
AntiAmplificationLimiter(anti_amplification.v, new) — RFC 9000§8.1's 3x pre-validation send limit, mirroring
flow_control.v'sFlowControlWindowshape. Standalone and tested; not yet wired intoa datagram-processing loop (that's 13d's job).
Deliberate scope limits:
connections) is out of scope — v1 only issues tokens via Retry.
deferred to 13d (needs a real listening socket to own a consumed-token
cache's lifetime) — the short window satisfies §8.1.4's "prevented OR
limited" replay requirement in the interim.
Found and flagged, not fixed here (separate task, out of scope for this
PR): while choosing a CSPRNG for the token nonce, discovered
conn.v'sdial()uses V's general-purposerandmodule (wyrand-backed, notcryptographically secure) for
original_dcid/scid/client_random—all security-relevant values that should use
crypto.randinstead (identicalAPI, OS-backed, already used elsewhere in this codebase). This is a real
gap in already-merged code (Phase 9, part of #28129), not Phase 13 work —
now being tracked and worked in a separate session/branch.
13c: Connection ID lifecycle — DONE (
bc5f108b9c)NewConnectionIdFrame/RetireConnectionIdFramewire codec(
frame.v) —encode_new_connection_id_frame/parse_new_connection_id_frameand their RETIRE_CONNECTION_IDcounterparts, types 0x18/0x19, previously falling through
parse_frame's generic "not yet implemented" branch. Enforces thetwo frame-local RFC 9000 §19.15 requirements (
retire_prior_to≤sequence_number; connection ID length in 1-20 bytes) on both theencode and decode sides, so a caller can't construct a frame this
module's own parser would then reject. Every OTHER §19.15/§19.16
requirement (zero-length-DCID prohibition, duplicate/conflicting
sequence numbers, a RETIRE_CONNECTION_ID referencing the current
packet's own DCID) needs connection state
parse_framedoesn'thave — deferred to the caller, the same division already
established for
HandshakeDoneFrame's role check.generate_stateless_reset_token(stateless_reset.v) — RFC 9000§10.3.2's recommended construction,
HMAC-SHA-256(static_key, connection_id)truncated to 16 bytes: a server-instance-localsecret plus the connection ID deterministically reproduces the SAME
token, so an endpoint that has lost all per-connection state (the
entire premise of a stateless reset) can still recompute it.
Cross-checked against
StatelessResetTracker.is_stateless_reset(already-existing, independently-written matching logic) — proving
a token this function generates is actually recognized by the exact
code that would validate it in production.
QuicConn.dispatch_one_rtt_frame(conn.v) now explicitlyacknowledges both new frame types instead of letting them fall
silently into the generic informational-hint else-arm — found during
self-review (a
match QuicFrameconsumer audit across the module):before this frame codec existed, these frame types could never reach
that dispatch at all, so the else-arm's swallow of them only became
a live, reachable code path as a direct consequence of this PR. Fixed
inline rather than left implicit.
Deliberately still out of scope (per
stateless_reset.v's ownlong-standing note, unchanged by 13c): driving an ACTIVE SET of usable
connection IDs — issuing more as the peer retires them,
active_connection_id_limitaccounting,CONNECTION_ID_LIMIT_ERRORenforcement. That full lifecycle exists to support connection migration,
already listed below as a separate, explicitly deferrable follow-up — 13c
ships the wire codec and the token primitive it depends on, not the state
machine that would consume them.
13d-1: Server-role handshake wiring — DONE (
3a97c5962f)QuicConngained real.server-role support:handshakesplit into?&Tls13ClientHandshake(client) and a newserver_handshake ?&Tls13ServerHandshake(server); role-aware directional key-selectionhelpers (
own_initial_keys/peer_initial_keys/own_handshake_keys/peer_handshake_keys, RFC 9001 §5.2's client/server key swap);is_handshake_confirmed()branches per RFC 9001 §4.1.2 (client:HANDSHAKE_DONE received; server: handshake complete — the two are the
SAME event only for the client).
dispatch_handshake_messagegained a server branch(
dispatch_server_handshake_message): bootstraps from the ClientHellovia
Tls13ServerHandshake.respond_to_client_hello, derivesHandshake/Application keys, applies peer transport-parameter side
effects — mirroring the client's existing per-state dispatch shape
rather than a parallel implementation.
drain_outgoingnow sends HANDSHAKE_DONE as soon as the server'shandshake completes (RFC 9001 §4.1.2's "MUST send... as soon as the
handshake is complete"), gated by a one-shot
handshake_done_sentflag, then immediately calls
on_handshake_confirmed— for a server,confirmed IS complete, so this ordering is non-circular.
accept.v:pub fn accept(raw_datagram []u8, params AcceptParams, now u64) !(&QuicConn, PollResult), the server-role counterpart todial(). Deliberately thin — bootstraps this connection's ownidentity and Initial-space keys from the client's first datagram, then
hands that same datagram to the already-tested
poll()foreverything else, rather than a separate decrypt/dispatch path to keep
in sync.
Two RFC-conformance bugs found and fixed via an adversarial multi-agent
review (4 independent lenses + an independent refutation pass on every
candidate finding) before this was pushed, both independently re-verified
by a second, focused agent after the fix:
to its own
original_dciduntil it has processed a reply from theserver — the client can't address a value (the server's freshly generated
scid) it hasn't learned yet.process_initial_or_handshake's DCID matchnow accepts a server-role connection's bootstrap ClientHello on that
basis, scoped narrowly to
role == .server && space == .initial.("MUST discard... when it first sends a Handshake packet") but
receive-triggered for a server ("MUST discard... when it first
successfully processes a Handshake packet"). The original diff applied
the client's send-based trigger to both roles; since
accept()'s singlepoll()call both processes the ClientHello AND sends the server's ownHandshake-space response flight, this discarded the server's Initial keys
before the client had sent anything back — silently dropping any
ClientHello retransmission and leaving the server unable to
PTO-retransmit its own lost first flight, stalling the handshake to idle
timeout on ordinary first-round-trip packet loss. Fixed by splitting into
a client-only send trigger (
build_handshake_packet) and a new serverreceive trigger (
process_initial_or_handshake, right after aHandshake-space packet from the client successfully decrypts).
Also closed a missing RFC 9000 §14.1 anti-amplification check found by the
same review:
accept()now rejects any datagram under the 1200-byte floorbefore doing any work, closing a reflection-amplification gap where a small,
address-spoofed trigger datagram could get a full multi-message response
flight.
Deliberate scope limits (documented in code, not hidden):
accept()does NOT decide Retry-vs-direct-accept policy (RFC 9000 §8.1) —that needs cross-connection-attempt state (has this address been seen
before? is the anti-amplification budget exhausted?) only a caller
tracking many attempts across many addresses can have. 13b's
AntiAmplificationLimiter/Retry machinery already exists for 13d-2'slistener to wire in before ever calling
accept().frame in a single packet — correct for this repo's own small test
certificate, but not fragmented across multiple packets if a real-world
certificate chain doesn't fit one packet's payload.
dial()'s ownClientHello flush has the identical shape but was never previously
exercised with anything large enough to expose it.
Out of scope for the remaining sub-phase:
h3_server.vwiring (13e).Connection migration (
PATH_CHALLENGE/PATH_RESPONSE) is a separate,explicitly deferrable follow-up. 0-RTT (Phase 14) and server push
(permanently disabled, RFC 9114 §7.2.7) stay out of scope regardless.
Test plan
./vnew -no-memory-limit -silent test vlib/net/quic/green (58/58)existing parse counterpart;
build_finishedadditionally checkedagainst the real RFC 8448 §3 Finished vector
Finished agreement between two independently-implemented roles
verified (positive + two negative cases)
expiry-boundary, and address-mismatch cases
post-validation unlimited cases
checks (retire_prior_to > sequence_number, CID length outside 1-20)
rejected on both encode and decode sides, truncated-buffer cases
zero-length-CID rejection, and cross-checked end-to-end against
StatelessResetTracker.is_stateless_resetcheck for each new function per Phase 0 step 6; 13c's pass included a
full
match QuicFrameconsumer audit across the module, which caughtthe
dispatch_one_rtt_framegap listed above)./vnew fmt -w/./vnew missdocon touched filesdial()/accept()integration test (accept_test.v) —genuine client-vs-server handshake including Certificate/
CertificateVerify chain verification against a freshly generated EC
cert (closes 13a's own documented gap) and a bidirectional stream
exchange, all real byte-level datagrams, no internal shortcuts
refutation pass) before push, surfacing the two RFC-conformance bugs
and the anti-amplification gap described above; each fix independently
re-verified by a second, focused adversarial pass after the fact
(Initial-key-discard timing at both the premature-discard and
correct-discard points) plus a dedicated rejection test for the
anti-amplification floor
Draft — 13a, 13b, 13c, and 13d-1 complete; next up is 13d-2 (UDP listener +
connection demux).